package com.jzzzb.distributedlock.aspect;

import com.jzzzb.distributedlock.annotation.DistributedLock;
import com.jzzzb.distributedlock.annotation.DistributedLockKey;
import com.jzzzb.distributedlock.redis.IRedissonService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁切面
 *
 * @author zhangbin
 * @date 2024/08/12
 */
@Aspect
@Slf4j
public class DistributedLockAspect {

    @Resource
    private IRedissonService redissonService;

    @Around("@annotation(com.jzzzb.distributedlock.annotation.DistributedLock)")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Exception {
        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();

        // 获取方法
        Method method = methodSignature.getMethod();

        // 获取方法上面的注解
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);

        // 获取操作描述的属性值
        String lockKey = distributedLock.lockKey();
        long holdTime = distributedLock.holdTime();
        long waitTime = distributedLock.waitTime();

        // method获取参数信息。 如果参数带有DistributedLockKey 注解，lockKey 拼接参数的value
        Parameter[] parameters = method.getParameters();
        // 获取参数值
        Object[] args = pjp.getArgs();
        if (parameters.length > 0) {
            for (int i = 0, length = parameters.length; i < length; i++) {
                DistributedLockKey distributedLockKey = parameters[i].getAnnotation((DistributedLockKey.class));
                if (null != distributedLockKey) {
                    String arg = String.valueOf(args[i]);
                    if (StringUtils.hasText(arg)) {
                        lockKey += ":" + arg;
                    }
                }
            }
        }

        log.info("lockKey: {}", lockKey);

        RLock lock = redissonService.getLock(lockKey);
        boolean flag = lock.tryLock(waitTime, holdTime, TimeUnit.SECONDS);
        if (!flag) {
            throw new RuntimeException("获取锁失败");
        }

        Object result;
        try {
            result = pjp.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

        return result;
    }
}
